<?php
/**
 * Imagecow PHP library
 *
 * Abstract core class extended by one of the available libraries (GD, Imagick)
 * Original code from phpCan Image class (http://idc.anavallasuiza.com/)
 *
 * PHP version 5.3
 *
 * @author Oscar Otero <http://oscarotero.com> <oom@oscarotero.com>
 * @license GNU Affero GPL version 3. http://www.gnu.org/licenses/agpl-3.0.html
 * @version 0.4 (2012)
 */

namespace Imagecow;

define('IMAGECOW_ERROR_LOADING', 1);
define('IMAGECOW_ERROR_FUNCTION', 2);
define('IMAGECOW_ERROR_INPUT', 3);

use Imagecow\ImageException;

abstract class Image
{
    protected $image;
    protected $quality = 90;
    protected $Error;

    /**
     * Static function to create a new Imagecow instance
     *
     * @param string $library The name of the image library to use (Gd or Imagick). If it's not defined, detects automatically the library to use.
     *
     * @return object The Imagecow instance
     *
     * @throws Exception if the image library does not exists.
     */
    public static function create ($library = null)
    {
        if (!$library) {
            $library = extension_loaded('imagick') ? 'Imagick' : 'Gd';
        }

        $class = '\\Imagecow\\Libs\\'.$library;

        if (class_exists($class)) {
            return new $class;
        }

        throw new _Exception('The image library is not valid');
    }



    /**
     * Static function to filter the transform operations according to client properties
     * Useful to generate responsive images
     *
     * @param string $client_properties The cookie value generated by Imagecow.js scripts with the client dimmensions.
     * @param string $operations        The operations to transform the image
     *
     * @return string The operations that matches with the client properties.
     */
    public static function getResponsiveOperations ($client_properties, $operations = '')
    {
        if (!$operations) {
            return '';
        }

        $client = array();

        foreach (explode('|', str_replace(' ', '', $client_properties)) as $client_properties) {
            $client_properties = explode(',', $client_properties);
            $client[array_shift($client_properties)] = $client_properties;
        }

        $width = isset($client['dimensions'][0]) ? intval($client['dimensions'][0]) : null;
        $height = isset($client['dimensions'][1]) ? intval($client['dimensions'][1]) : null;

        $transform = array();

        foreach (explode(';', str_replace(' ', '', $operations)) as $operation) {
            if (empty($operation)) {
                continue;
            }

            if (strpos($operation, ':') === false) {
                $transform[] = $operation;
                continue;
            }

            if (!isset($width) || !isset($height)) {
                continue;
            }

            list($rules, $operation) = explode(':', $operation, 2);

            foreach (explode(',', $rules) as $rule) {
                $rule = explode('=', $rule, 2);
                $value = intval($rule[1]);

                switch ($rule[0]) {
                    case 'max-width':
                        if ($width > $value) {
                            continue 2;
                        }
                        break;

                    case 'min-width':
                        if ($width < $value) {
                            continue 2;
                        }
                        break;

                    case 'width':
                        if ($width != $value) {
                            continue 2;
                        }
                        break;

                    case 'max-height':
                        if ($height > $value) {
                            continue 2;
                        }
                        break;

                    case 'min-height':
                        if ($height < $value) {
                            continue 2;
                        }
                        break;

                    case 'height':
                        if ($height != $value) {
                            continue 2;
                        }
                        break;
                }

                $transform[] = $operation;
            }
        }

        return implode('|', $transform);
    }


    /**
     * Gets the original image
     *
     * @return mixed Depending of the library used: the Imagick instance, GD resource or null if no image has been loaded
     */
    public function getImage ()
    {
        return $this->image;
    }



    /**
     * Gets the error in a ImageException instance
     *
     * @return ImageException object  The error exception or null if there is not errors
     */
    public function getError ()
    {
        return $this->Error;
    }



    /**
     * Sets a new error for the image
     *
     * @param string $message The message of the error
     * @param int    $code    The code of the message. It can be one of the following constants: IMAGECOW_ERROR_LOADING, IMAGECOW_ERROR_FUNCTION, IMAGECOW_ERROR_INPUT
     *
     * @return $this
     */
    public function setError ($message = '', $code = null)
    {
        $this->Error = new \Imagecow\ImageException($message, $code);

        return $this;
    }


    /**
     * Define the image compression quality for jpg images
     *
     * @param int $quality The quality (from 0 to 100)
     *
     * @return $this
     */
    public function setCompressionQuality ($quality)
    {
        $quality = intval($quality);

        if ($quality < 0) {
            $this->quality = 0;
        } elseif ($quality > 100) {
            $this->quality = 100;
        } else {
            $this->quality = $quality;
        }

        return $this;
    }



    /**
     * Reads the EXIF data from a JPEG and returns an associative array
     *
     * @return array The data where the array indexes are the header names and array values the associated values. Returns false on error
     */
    public function getExifData ()
    {
        $filename = $this->getFilename();

        return isset($filename) ? exif_read_data($filename) : false;
    }



    /**
     * Transform the image executing various operations of crop, resize, resizeCrop and format
     *
     * @param string $operations The string with all operations separated by "|".
     *
     * @return $this
     */
    public function transform ($operations = '')
    {
        if (!$operations) {
            return $this;
        }

        $operations = $this->getOperations($operations);

        foreach ($operations as $operation) {
            call_user_func_array(array($this, $operation['function']), $operation['params']);
        }

        return $this;
    }



    /**
     * Converts a string with operations in an array
     *
     * @param string $operations The operations string
     *
     * @return array The operation width the function name and the parameters
     */
    private function getOperations ($operations)
    {
        $valid_operations = array('resize', 'resizeCrop', 'crop', 'format');
        $operations = explode('|', str_replace(' ', '', $operations));
        $return = array();

        foreach ($operations as $operations) {
            $params = explode(',', $operations);
            $function = trim(array_shift($params));

            if (!in_array($function, $valid_operations)) {
                $this->setError('The transform function "'.$function.'" is not valid', IMAGECOW_ERROR_INPUT);
                continue;
            }

            $return[] = array(
                'function' => $function,
                'params' => $params
            );
        }

        return $return;
    }



    /**
     * Send the HTTP header with the content-type, output the image data and die.
     */
    public function show ()
    {
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
                header('Content-Type: '.$mimetype);
                die($string);
        }
    }



    /**
     * Calculates the point position according with the image dimmensions.
     *
     * @param string/int $position The value of the position. It can be number (pixels), percentaje or one of the available keywords (top,left,bottom,right,middle,center)
     * @param number     $size     The size of the new cropped/resized image.
     * @param number     $canvas   The size of the old image
     *
     * @return integer The position of the point in pixeles.
     */
    protected function position ($position, $size, $canvas)
    {
        if (is_int($position)) {
            return $position;
        }

        switch ($position) {
            case 'top':
            case 'left':
                $position = 0;
                break;

            case 'middle':
            case 'center':
                $position = ($canvas/2) - ($size/2);
                break;

            case 'right':
            case 'bottom':
                $position = $canvas - $size;
                break;

            default:
                $position = $this->getSize($position, $canvas);
        }

        return $position;
    }


    /**
     * Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions.
     *
     * @param int/string $width  The new width in number (pixels) or percentaje
     * @param int/string $height The new height in number (pixels) or percentaje
     * @param int/string $x      The "x" position where start to crop. It can be number (pixels), percentaje or one of the available keywords (left,center,right)
     * @param int/string $y      The "y" position where start to crop. It can be number (pixels), percentaje or one of the available keywords (top,middle,bottom)
     *
     * @return $this
     */
    public function resizeCrop ($width, $height, $x = 'center', $y = 'middle')
    {
        $width = $this->getSize($width, $this->getWidth());
        $height = $this->getSize($height, $this->getHeight());

        if (($width === 0) || ($height === 0)) {
            return false;
        }

        $width_resize = ($width / $this->getWidth()) * 100;
        $height_resize = ($height / $this->getHeight()) * 100;

        if ($width_resize < $height_resize) {
            $this->resize(0, $height);
        } else {
            $this->resize($width, 0);
        }

        $this->crop($width, $height, $x, $y);

        return $this;
    }



    /**
     * Get the size of the image.
     *
     * @param string/int $value      The size in numbers (pixels) or percentaje.
     * @param int        $total_size The total size of the image (used to calculate the percentaje)
     *
     * @return integer The calculated value
     */
    protected function getSize ($value, $total_size)
    {
        if (substr($value, -1) === '%') {
            return ($total_size/100) * intval(substr($value, 0, -1));
        }

        return intval($value);
    }

    /**
     * Check if the image must be enlarged or not (if the new dimmensions are bigger than original)
     *
     * @param int $new_size      The new size of the image
     * @param int $original_size The original size of the image
     *
     * @return boolean True if the image must be enlarged and false if not.
     */
    protected function enlarge ($new_size, $original_size)
    {
        if ($new_size && $new_size > $original_size) {
            return true;
        }

        return false;
    }
}
